/*
 * Copyright 2022 The Open Islands Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openislands.oi.manager;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.openislands.oi.bootstrap.Bootstrap;
import org.springframework.context.ApplicationContext;
import org.openislands.oi.constant.StateInterface;
import org.openislands.oi.util.ReflectUtils;
import org.openislands.oi.util.ValueCopyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.BadSqlGrammarException;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

public class GeneralManager<M extends BaseMapper<DO>, DO> extends ServiceImpl<M, DO> {
    private static final Logger logger = LoggerFactory.getLogger(GeneralManager.class);

    @Resource
    private ApplicationContext applicationContext;

    public GeneralManager(M baseMapper) {
        this(baseMapper, null);
    }

    public GeneralManager(M baseMapper, Class<DO> entityClass) {
        super.baseMapper = baseMapper;
        super.entityClass = entityClass;
        if (entityClass == null) {
            super.entityClass = ReflectUtils.findInterfacesGenericClass(baseMapper.getClass(), BaseMapper.class, 0);
        }
    }

    @SuppressWarnings("unchecked")
    public final DO copy(Object dto) {
        return (DO) ValueCopyUtils.copyProperties(dto, entityClass);
    }

    @SafeVarargs
    public final <Res> Boolean delete(DO odo, SFunction<DO, Res>... functions) {
        if (functions.length == 0) {
            return false;
        } else {
            final LambdaUpdateWrapper<DO> eq = new UpdateWrapper<DO>().lambda();
            for (SFunction<DO, Res> function : functions) {
                eq.eq(function, function.apply(odo));
            }
            return remove(eq);
        }
    }

    public final Boolean delete(Consumer<LambdaUpdateWrapper<DO>> wrapperConsumer) {
        final LambdaUpdateWrapper<DO> eq = new UpdateWrapper<DO>().lambda();
        wrapperConsumer.accept(eq);
        return remove(eq);
    }

    public final Boolean update(Consumer<LambdaUpdateWrapper<DO>> wrapperConsumer) {
        return update(this.copy(new Object()), wrapperConsumer);
    }

    public final Boolean update(DO odo, Consumer<LambdaUpdateWrapper<DO>> wrapperConsumer) {
        final LambdaUpdateWrapper<DO> eq = new UpdateWrapper<DO>().lambda();
        wrapperConsumer.accept(eq);
        return update(odo, eq);
    }

    @SafeVarargs
    public final <DTO, Res> Boolean updateAnyLikeTask(DTO srcDTO, Function<DO, StateInterface> getState,
                                                      Consumer<DO> setState, SFunction<DO, Res>... functions) {
        if (functions.length == 0) {
            return false;
        }
        final DO updateDO = this.copy(srcDTO);
        StateInterface state = getState.apply(updateDO);
        if (state != null) {
            logger.warn("ignore update state and use the update state interface directly");
            setState.accept(updateDO);
        }
        return update(updateDO, uw -> {
            for (SFunction<DO, Res> function : functions) {
                uw.eq(function, function.apply(updateDO));
            }
        });
    }

    @SafeVarargs
    public final <DTO, Res> Boolean updateAnyStatelessTask(DTO srcDTO, Function<DO, StateInterface> getState,
                                                      Consumer<DO> setState, SFunction<DO, Res>... functions) {
        if (functions.length == 0) {
            return false;
        }
        final DO updateDO = this.copy(srcDTO);
        StateInterface state = getState.apply(updateDO);
        if (state != null) {
            logger.warn("ignore update state and use the update state interface directly");
            setState.accept(updateDO);
        }
        return update(updateDO, uw -> {
            for (SFunction<DO, Res> function : functions) {
                uw.eq(function, function.apply(updateDO));
            }
        });
    }

    @SuppressWarnings("unchecked")
    public <DTO> List<DTO> queryAnyLikeDTO(DTO srcDTO) {
        try {
            final DO queryDO = this.copy(srcDTO);
            final List<DO> dos = list(new QueryWrapper<>(queryDO));
            if (dos.isEmpty()) {
                return new ArrayList<>();
            } else {
                Class<DTO> dtoClass = (Class<DTO>) srcDTO.getClass();
                return ValueCopyUtils.copyBuilderList(dos, builder -> builder.build(dtoClass));
            }
        }catch (BadSqlGrammarException badSqlGrammarException){
            logger.error("sql error, please start apiserver first and check database.", badSqlGrammarException);
            Bootstrap.exit();
            throw badSqlGrammarException;
        } catch (Exception e) {
            logger.error(e.getClass().getName());
            throw new RuntimeException(e);
        }
    }

    public List<DO> queryAnyList(Consumer<LambdaQueryWrapper<DO>> wrapperConsumer) {
        try {
            LambdaQueryWrapper<DO> queryWrapper = new QueryWrapper<DO>().lambda();
            wrapperConsumer.accept(queryWrapper);
            return list(queryWrapper);
        }catch (BadSqlGrammarException badSqlGrammarException){
            logger.error("sql error, please start apiserver first and check database.", badSqlGrammarException);
            Bootstrap.exit();
            throw badSqlGrammarException;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public <DTO> DTO queryOneDTO(DTO srcDTO) {
        try{
            final DO rdo = getOne(new QueryWrapper<>(this.copy(srcDTO)));
            return ValueCopyUtils.copyProperties(rdo, (Class<DTO>) srcDTO.getClass());
        }catch (BadSqlGrammarException badSqlGrammarException){
            logger.error("sql error, please start apiserver first and check database.", badSqlGrammarException);
            Bootstrap.exit();
            throw badSqlGrammarException;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public DO queryOne(Consumer<LambdaQueryWrapper<DO>> wrapperConsumer) {
        LambdaQueryWrapper<DO> queryWrapper = new QueryWrapper<DO>().lambda();
        wrapperConsumer.accept(queryWrapper);
        return getOne(queryWrapper);
    }
}
